﻿using System;
using System.Collections.Generic;
using System.Linq;
using Foundation.Sugar;
using Protocol.Data;
using Protocol.Transport;

namespace Protocol.Authorization
{
    public class Authorizer : SugarBase
    {
        public DataChannel Channel { get; private set; }
        public byte[] Salt { get; set; }
        public uint ServerTime { get; set; }
        public byte[] AuthKey { get; set; }
        public byte[] AuthKeyHash { get; set; }

        public Authorizer(DataChannel channel)
        {
            Channel = channel;
        }

        public bool Authorize()
        {
            for (var i = 0; i < 16; i++)
            {
                new pqRequest {Nonce = Guid.NewGuid().ToByteArray()}.Send(Channel);
                var pqResponce = Channel.Receive().Of<pqResponce>();
                var pq = pqResponce.PQ;
                var p = PrimeUtils.findSmallMultiplier(pq);
                var q = pq/p;
                var pqInner = new pqInner
                {
                    Pq = pqResponce.PQ,
                    P = p,
                    Q = q,
                    Nonce = pqResponce.Nonce,
                    ServerNonce = pqResponce.ServerNonce,
                    NewNonce = Sugar.GetRandomBytes(32)
                };

                var data = new List<byte>();
                var pqInnerBytes = pqInner.ToBytes();
                data.AddRange(pqInnerBytes.Hash());
                data.AddRange(pqInnerBytes);
                data.AddRange(Sugar.GetRandomBytes(255 - data.Count));
                var keySet = KeySet.PublicKeySets.First(s => s.Fingerprint == pqResponce.Fingrprints[0]);
                var modulus = keySet.Modulus;
                var exponent = keySet.Exponent;
                var message = new BigInteger(data.ToArray());
                var encriptedData = message.modPow(exponent, modulus);
                new dhRequest
                {
                    P = p,
                    Q = q,
                    Nonce = pqResponce.Nonce,
                    ServerNonce = pqResponce.ServerNonce,
                    FingerPrint = pqResponce.Fingrprints[0],
                    EncriptedData = encriptedData.getBytes(),
                }.Send(Channel);
                var dhResponce = Channel.Receive();
                if (!(dhResponce is ResDhOk)) continue;

                var dhResponceOk = (ResDhOk) dhResponce;
                var nonce = pqInner.Nonce;
                var newNonce = pqInner.NewNonce;
                var serverNonce = pqInner.ServerNonce;
                var shaNn = Hash(Concat(newNonce, newNonce));
                var shaNs = Hash(Concat(newNonce, serverNonce));
                var shaSn = Hash(Concat(serverNonce, newNonce));
                var tmpAesKey = Concat(shaNs, shaSn.Take(12)).ToArray();
                var tmpAesIv = Concat(shaSn.Skip(12).Take(8), shaNn, newNonce.Take(4)).ToArray();
                var encriptedBytes = dhResponceOk.EncriptedAnswer;
                var answerWithHash = encriptedBytes.AES256IGEDecrypt(tmpAesIv, tmpAesKey);
                var dhInnerHash = answerWithHash.Take(20).ToArray();
                var answer = answerWithHash.Skip(20).ToArray();
                var dhInner = answer.FromBytes().Of<InnerServerDh>();
                if (!dhInnerHash.SequenceEqual(dhInner.ToBytes().Hash())) continue;

                for (var j = 0; j < 16; j++)
                {
                    var bytes = Sugar.GetRandomBytes(256);
                    var b = new BigInteger(bytes.Reverse().ToArray());
                    if (b < 0) b = -b;

                    var g = (BigInteger) dhInner.G;
                    var ga = dhInner.Ga;
                    var dhPrime = dhInner.DhPrime;
                    var gb = g.modPow(b, dhPrime);
                    var authKeyVal = ga.modPow(b, dhPrime);
                    var authKey = AlignKeyZero(authKeyVal.getBytes(), 256);
                    var authAuxHash = Hash(authKey).Take(8).ToArray();
                    var dhInnerClient = new ClientDhInner
                    {
                        Nonce = nonce,
                        ServerNonce = serverNonce,
                        RetryId = j,
                        Gb = gb
                    };

                    var innerData = dhInnerClient.ToBytes();
                    var innerDataWithHash = Concat(Hash(innerData), innerData);
                    var dataWithHashEnc = innerDataWithHash.AlignBy(16).AES256IGEEncrypt(tmpAesIv, tmpAesKey);
                    var reqDhGenResult = new ReqSetDhClientParams
                    {
                        Nonce = nonce,
                        ServerNonce = serverNonce,
                        EncriptedData = dataWithHashEnc
                    };

                    Channel.Send(reqDhGenResult);
                    var result = Channel.Receive();
                    var dhGenOk = result as ResDhGenOk;
                    if (dhGenOk == null) continue;

                    var newNonceHash = Substring(Hash(newNonce, new byte[] {1}, authAuxHash), 4, 16);
                    if (!dhGenOk.NewNonceHash1.SequenceEqual(newNonceHash)) continue;

                    ServerTime = dhInner.ServerTime;//1000 << 32
                    var d = Sugar.FromUnixTimeStamp(ServerTime);              
                    TimeManager.Init(d, DateTime.Now);
                    AuthKey = authKey;
                    AuthKeyHash = authAuxHash;
                    Salt = new byte[8];
                    for (var k = 0; k < 8; k++)
                    {
                        Salt[k] = (byte) (newNonce[k] ^ serverNonce[k]);
                    }

                    return true;
                }
            }

            return false;
        }
    }
}